<?php
/**
 *
 * Loads form hints from a Solar_Sql_Model object
 * 
 * @category Abovo
 * 
 * @package Abovo_Form_Load
 * 
 * @author Antti Holvikari <anttih@gmail.com>
 * 
 * @author Rodrigo Moraes <rodrigo.moraes@gmail.com>
 * 
 * @license http://opensource.org/licenses/bsd-license.php BSD
 * 
 * @version $Id$
 * 
 */

/**
 *
 * Loads form hints from a Solar_Sql_Model object
 *
 * @category Abovo
 *
 * @package Abovo_Form_Load
 *
 */
class Abovo_Form_Load_Model extends Solar_Base {

    /**
     *
     * Loads Solar_Form elements based on Solar_Sql_Table columns.
     *
     * @param Solar_Sql_Table $record Load form elements from this record.
     *
     * @param array $list Which table columns to load as form elements, default '*'.
     *
     * @param string $array_name Load the table columns as elements of this
     * array-name within the form.
     *
     * @return Solar_Form|false Solar_Form object, or boolean false on error.
     *
     */
    public function fetch($record, $list = '*', $array_name = null)
    {
        if (! $record instanceof Solar_Sql_Model) {
            throw $this->_exception('ERR_NOT_MODEL_OBJECT');
        }

        // if not specified, set the array_name to the table name
        if (empty($array_name)) {
            $array_name = $record->getTableName();
        }

        $cols_info = $record->getTableCols();

        // all columns known by the table
        $all_cols = array_keys($cols_info);

        // special condition: if looking for '*' columns,
        // get the list of all the table columns.
        if ($list == '*') {
            $list = $all_cols;
        } else {
            settype($list, 'array');
        }

        // get model filters
        $filters = $record->getFilters();

        // loop through the list of requested columns and collect elements
        $elements = array();
        foreach ($list as $name => $info) {

            // if $name is integer, $info is just a column name,
            // and there is no added element info.
            if (is_int($name)) {
                $name = $info;
                $info = array();
            } else {
                settype($info, 'array');
            }

            // is the column name in the table?
            if (! in_array($name, $all_cols)) {
                continue;
            }

            // initial set of element info based on the table column
            $base = array(
                'name'     => $array_name . '[' . $name . ']',
                'type'     => null,
                'label'    => $record->locale(strtoupper("LABEL_$name")),
                'descr'    => $record->locale(strtoupper("DESCR_$name")),
                'value'    => $record->$name,
                'require'  => $cols_info[$name]['require'],
                'disable'  => $cols_info[$name]['primary'],
                'options'  => array(),
                'attribs'  => array(),
                'feedback' => array(),
                'filter'   => array(),
            );
            $info = array_merge($base, $info);


            if (! empty($filters[$name])) {
                foreach ($filters[$name] as $f) {
                    $t = array();

                    // take method name and add to filter list
                    $method = array_shift($f);
                    $t[] = $method;

                    // did the filter sanitize, or did it validate?
                    $type = strtolower(substr($method, 0, 8));

                    if ($type == 'validate') {
                        // add invalidation feedback
                        $tmp = preg_replace('/([a-z])([A-Z])/', '$1_$2', $method);
                        $tmp = strtoupper($tmp);
                        $t[] = $record->locale($tmp);

                        if (! empty($f)) {
                            // take the rest of the filter rule
                            $t = array_merge($t, $f);
                        }
                    }
                    // add filter
                    $info['filter'][] = $t;
                }
            }

            // make primary keys hidden and disabled
            if ($cols_info[$name]['primary']) {
                $info['type'] = 'hidden';
                $info['disable'] = true;
            }

            // pick an element type based on the column type
            if (empty($info['type'])) {
                // base the element type on the column type.
                switch ($cols_info[$name]['type']) {

                case 'bool':
                    $info['type'] = 'checkbox';
                    break;

                case 'clob':
                    $info['type'] = 'textarea';
                    break;

                case 'date':
                case 'time':
                case 'timestamp':
                    $info['type'] = $cols_info[$name]['type'];
                    break;

                default:

                    // make 'id' and '*_id' columns hidden
                    if ($name == 'id' || substr($name, -3) == '_id') {
                        $info['type'] = 'hidden';
                    }

                    // if there is a validation for 'inList' or 'inKeys',
                    // make this a select element.
                    foreach ($info['filter'] as $v) {
                        if ($v[0] == 'validateInKeys' || $v[0] == 'validateInList') {
                            $info['type'] = 'select';
                            break;
                        }
                    }

                    // if type is still empty, make it text.
                    if (empty($info['type'])) {
                        $info['type'] = 'text';
                    }
                    break;
                }
            }

            // add filters based on column type, but only if
            // not hidden or disabled
            if ($info['type'] != 'hidden' && ! $info['disable'] &&
                empty($info['filter'])) {

                $method = null;

                switch ($cols_info[$name]['type']) {
                case 'date':
                    $method = 'IsoDate';
                    break;
                case 'time':
                    $method = 'IsoTime';
                    break;
                case 'timestamp':
                    $method = 'IsoTimestamp';
                    break;
                case 'smallint':
                case 'int':
                case 'bigint':
                    $method = 'Integer';
                    break;
                }

                if ($method) {
                    $code = 'VALID_' . strtoupper($method);
                    $info['filter'] = array(
                        array('validate' . $method, $this->locale($code))
                    );
                }
            }

            // set up options for checkboxes if none specified
            if ($info['type'] == 'checkbox' && empty($info['options'])) {
                // look for 'inKeys' or 'inList' validation.
                foreach ($info['filter'] as $f) {
                    if ($f[0] == 'validateInKeys' || $f[0] == 'validateInList') {
                        $info['options'] = $this->_autoOptions($f[0], $f[2]);
                        break;
                    }
                }
                // if still empty, set to 1,0
                if (empty($info['options'])) {
                    $info['options'] = array(1,0);
                }
            }

            // set up options for select and radio if none specified
            if (($info['type'] == 'select' || $info['type'] == 'radio') &&
                empty($info['options'])) {
                // look for 'inKeys' or 'inList' validation.
                foreach ($info['filter'] as $f) {
                    if ($f[0] == 'validateInKeys' || $f[0] == 'validateInList') {
                        $info['options'] = $this->_autoOptions($f[0], $f[2]);
                        break;
                    }
                }
            }

            // for text elements, set up maxlength if none specified
            if ($info['type'] == 'text' &&
                empty($info['attribs']['maxlength']) &&
                $cols_info[$name]['size'] > 0) {
                /** @todo Add +1 or +2 to 'size' for numeric types? */
                $info['attribs']['maxlength'] = $cols_info[$name]['size'];
            }

            // if no label specified, set up based on element name
            if (empty($info['label'])) {
                $info['label'] = $info['name'];
            }

            // keep the element
            $elements[$info['name']] = $info;
        }

        $result = array(
            'attribs'  => array(),
            'elements' => $elements
        );

        return $result;

    }

    /**
     *
     * Builds an option list from inKeys and inList validations.
     *
     * The 'inKeys' options are not changed.
     *
     * The 'inList' options are generally sequential, so the label
     * and the value are made to be identical (based on the label).
     *
     * @param string $type The validation type, 'inKeys' or 'inList'.
     *
     * @param array $opts The options provided by the validation.
     *
     * @return array
     *
     */
    protected function _autoOptions($type, $opts)
    {
        // leave the labels and values alone
        if ($type == 'validateInKeys') {
            return $opts;
        }

        // make the form display the labels as both labels and values
        if ($type == 'validateInList') {
            $vals = array_values($opts);
            return array_combine($vals, $vals);
        }
    }
}